I
IP, Interface Segregation Principle)對於客戶端,切分成許多個小型的介面,比一個通用的介面好,除此之外,介面不應該定義不相關的方法
以 Typescript 來舉例子說明(因為 Typescript 有 Interface 關鍵字):
interface MyClock {
currentTime: Date; // 定義 currentTime 需為 Date 形式
setTime(d: Date); // 定義 setTime 這個抽象方法
}
interface MyAlertClock {
alertWhenTimeout: Function // 定義 alertWhenTimeout 需為 Function 形式
}
class Clock implements MyClock, MyAlertClock{
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
alertWhenTimeout() {
if ( this.currentTime <= Date.now() ) {
console.log('time has timeout!');
}
}
這裏的時鐘要實作(implements)MyClock 這個介面,還需要先有 currentTime 才能 setTime,但是只要實現 MyClock 這個介面,Clock 就是一個正常的時鐘,介面之間不互相干擾(MyClock 與 MyAlertClock各自獨立),像 MyAlertClock 算是一個增強的介面,根據需要而實現。
而 React 類似的做法是依靠 PropTypes 以及搭配 DefaultProps。
舉例而言:
class ProductTable extends Component {
...
render() {
const product = {id: 1, content: '杯子', price: 200};
return (
<div>
<ProductDetail product={product}
</div>
)
}
}
class ProductDetail extends Component {
static propTypes = {
product: PropTypes.object.isRequired,
};
render() {
return (
<tr>
<td>Id: {this.props.product.id}</td>
<td>Content: {this.props.product.content}</td>
</tr>
)
}
}
上方這個例子,可以很明顯的看見 ProductTable 是將整個 product (Object) 傳進了 ProductRow ,但事實上 ProductRow 真正使用的值只有 Id 與 Content,如果今天我們要做測試,我們就需要 Mock 整個 Product不然就會出現問題,而介面隔離原則告訴我們將介面切分到最細,不要有不相關的方法。
以下為更改後程式碼:
class ProductTable extends Component {
...
render() {
const product = {id: 1, content: '杯子', price: 200};
return (
<div>
...
<ProductDetail id={product.id} name={product.content}/>
...
</div>
);
}
...
}
class ProductDetail extends Component {
static propTypes = {
id: PropTypes.number.isRequired,
content: PropTypes.string.isRequired,
};
render() {
return (
<tr>
<td>Id: {this.props.id}</td>
<td>Content: {this.props.content}</td>
</tr>
)
}
}
D
IP, Dependency Inversion Principle)用戶端參照的必須是介面(抽象)而不是物件
舉例而言:
const Class = ({classroom, grade}) => (
<li>{classroom}'s grade is {grade}</li>
)
const ListClass = ({data}) => (
<ul>{
data.map(item=>(
<Class key={item.name}
classroom={item.classroom} grade={item.grade} />
))
}
</ul>
);
ReactDOM.render(
<ListClass data={[
{classroom:"301",grade:"三年級"},
{classroom:"201",grade:"二年級"}
]} />,
document.getElementById('root')
);
乍看之下,這寫法是沒問題的,但如果這時候有另外一個元件也要使用 ListClass
時並且希望用不同呈現方式,這時候就會有問題了,可能就不能複用 ListClass
這個元件而要另外新增,因此這時候應該使用的是依賴倒轉原則,使得 ListClass 不要依賴於物件而是依賴於抽象。
const ListClass = ({data, ItemComponent}) => (
<ul>{
data.map(item=>(
<ItemComponent key={item.name}
{...item} />
))
}
</ul>
); // 這裏的 ItemComponent 就是抽象
ReactDOM.render(
<ListClass data={[
{classroom:"301",grade:"三年級"},
{classroom:"201",grade:"二年級"}
]}
ItemComponent={Class}/>,
document.getElementById('root')
);